昨天我們提到了狀態管理的基本功:InheritedWidget
今天談談進階版的InheritedWidget:Prodiver
Provider是一個套件
不只是Flutter Favorite而已
更是Flutter 12125 個套件裡面
最多人喜歡的(唯一超過兩千個like)
接下來我們就趕快來看看Prodiver該如何使用吧
通常我們以前可能會用NotificationCenter來完成上述情境☘️☘️☘️
換成Provider會像這樣子
notifyListeners()
去通知大家class LoginChangeNotifier with ChangeNotifier {
//Flutter沒有private修飾子, 用下底線開頭表示私有
bool _isLogin = false;
bool get isLogin => _isLogin;
//因為Provider好像沒有提供onChange之類的callback, 所以自行定義
VoidCallback onLogin;
VoidCallback onLogout;
loginToggle() {
_isLogin = !_isLogin;
notifyListeners();
if (isLogin) {
onLogin();
} else {
onLogout();
}
}
}
return MultiProvider(
child: tabScaffold,
providers: [
ChangeNotifierProvider.value(
value: LoginChangeNotifier()
)
],
);
listen
參數/// If [listen] is
true
(default), later value changes will trigger a new
/// [State.build] to widgets, and [State.didChangeDependencies] for
/// [StatefulWidget].
我的理解是當你發動時
狀態已經自己管理了
就不需要接受通知
故為false
Switch(
value: isLogin,
onChanged: (isOn) {
setState(() {
isLogin = isOn;
});
Provider.of<LoginChangeNotifier>(context, listen: false).loginToggle();
},
)
A. 一樣使用Provider.of(這時就要listen)
Provider.of<LoginChangeNotifier>(context).onLogout = (){
Navigator.pop(context);
};
B. 使用Consumer類別
Consumer<LoginChangeNotifier>(
child: Text("如果登出就會踢回前一頁"),
builder: (context, LoginChangeNotifier loginModel, widget){
loginModel.onLogout = () {
Navigator.pop(context);
};
//這邊return出去的才是最後顯示的widget
return Row(children: [
widget, //這個widget就是上面的child
Text(" :)")
], mainAxisAlignment: MainAxisAlignment.center);
},
)
有什麼差別呢?
根據等等文末推薦的資料
首先这证明了 Provider.of(context) 会导致调用的 context 页面范围的刷新。
那么第二个页面刷新没有呢? 刷新了,但是只刷新了 Consumer 的部分,甚至连浮动按钮中的 Icon 的不刷新我们都给控制了。
你可以在 Consumer 的 builder 方法中验证,这里不再啰嗦
假如你在你的应用的 页面级别 的 Widget 中,使用了 Provider.of(context)。会导致什么后果已经显而易见了,每当其状态改变的时候,你都会重新刷新整个页面。
虽然你有 Flutter 的自动优化算法给你撑腰,但你肯定无法获得最好的性能。
所以在这里我建议各位尽量使用 Consumer 而不是 Provider.of(context) 获取顶层数据。
簡言之就是Consumer只刷新局部Provider.of刷新整頁
還記得前面提到的胡椒跟鹽巴嗎?
在code裡面
我就不特別解說了
(那邊也是花了點時間研究...)
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:icofont_flutter/icofont_flutter.dart';
import 'package:provider/provider.dart';
class LessonPageProvider extends StatefulWidget {
@override
_LessonPageProviderState createState() => _LessonPageProviderState();
}
class _LessonPageProviderState extends State<LessonPageProvider> {
bool isLogin = false;
int currentIndex = 0;
final pages = [
PushNextPage(Colors.orangeAccent, showAppBar: true),
PushNextPage(Colors.black12),
PushNextPage(Colors.brown),
];
final items = [
BottomNavigationBarItem(icon: Icon(IcoFontIcons.brandMacOs), label: "甲"),
BottomNavigationBarItem(icon: Icon(IcoFontIcons.brandMacOs), label: "乙"),
BottomNavigationBarItem(icon: Icon(IcoFontIcons.brandMacOs), label: "丙"),
];
final keys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>()
];
@override
Widget build(BuildContext context) {
final tabScaffold = CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: items,
onTap: (idx){
if (idx == currentIndex) {
keys[currentIndex].currentState.popUntil((route) => route.isFirst);
}
currentIndex = idx;
},
),
tabBuilder: (ctx, idx){
return CupertinoTabView(
navigatorKey: keys[idx],
builder: (BuildContext context) =>
CupertinoPageScaffold(
child: pages[idx],
navigationBar: idx != 1 ? null : CupertinoNavigationBar(
middle: Text("庫比蒂諾"),
),
),
);
},
);
return MultiProvider(
child: tabScaffold,
providers: [
ChangeNotifierProvider.value(
value: LoginChangeNotifier()
)
],
);
}
}
//-
class PushNextPage extends StatefulWidget {
Color backgroundColor = Colors.white;
bool showAppBar;// = false; //這邊給預設值沒用...要寫在建構子
PushNextPage(this.backgroundColor, {this.showAppBar = false});
@override
_PushNextPageState createState() => _PushNextPageState();
}
class _PushNextPageState extends State<PushNextPage> {
bool isLogin = false;
final scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
Widget nextPage = PopPreviousPage();
final center = Container(
color: widget.backgroundColor,
alignment: Alignment.center,
child: CupertinoButton(
child: Icon(Icons.next_week, size: 30),
onPressed: (){
if (Provider.of<LoginChangeNotifier>(context).isLogin) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => nextPage)
);
} else {
scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text("只有登入後才可進入下一頁喔 :)")
)
);
}
},
)
);
final drawer = Drawer(
child: Column(
children: [
Container(
padding: EdgeInsets.only(top: 30, bottom: 16),
child: Icon(IcoFontIcons.waiterAlt, size: 100),
),
ListTile(
title: Text(isLogin ? "登出" : "登入",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20
),
),
trailing: Switch(
value: isLogin,
onChanged: (isOn) {
//這句放在setState前面就會壞掉, 也太奇怪了吧....
//Provider.of<LoginChangeNotifier>(context, listen: false).loginToggle();
setState(() {
isLogin = isOn;
});
Provider.of<LoginChangeNotifier>(context, listen: false).loginToggle();
},
)
)
],
)
);
return Scaffold(
key: scaffoldKey,
appBar: widget.showAppBar ? AppBar(title: Text("瑪提利尤")) : null,
drawer: widget.showAppBar ? SizedBox(width: 200, child: drawer) : null,
body: center,
);
}
}
//-
class PopPreviousPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Consumer<LoginChangeNotifier>( //用Consumer只刷新這邊
child: Text("如果登出就會踢回前一頁"),
builder: (context, LoginChangeNotifier loginModel, widget){
loginModel.onLogout = () {
Navigator.pop(context);
};
//這邊return出去的才是最後顯示的widget
return Row(children: [
widget, //這個widget就是上面的child, child可以保持不重刷
Text(" :)")
], mainAxisAlignment: MainAxisAlignment.center);
},
)
)
);
}
}
//-
class LoginChangeNotifier with ChangeNotifier {
bool _isLogin = false;
bool get isLogin => _isLogin;
VoidCallback onLogin;
VoidCallback onLogout;
loginToggle() {
_isLogin = !_isLogin;
notifyListeners();
if (isLogin) {
onLogin();
} else {
onLogout();
}
}
}
最後推薦一個超詳細的Provider大全:Flutter | 状态管理指南篇——Provider
講得很仔細
內容又超多(目錄都快比我今天的文章還多了 不誇張)
推薦給想深入Provider的朋友
下集預告:Notification